package com.ikingtech.platform.service.system.user.controller;

import com.ikingtech.framework.sdk.authenticate.operation.IdentityOps;
import com.ikingtech.framework.sdk.base.model.BatchParam;
import com.ikingtech.framework.sdk.cache.constants.CacheConstants;
import com.ikingtech.framework.sdk.context.event.SystemInitEvent;
import com.ikingtech.framework.sdk.context.event.TenantDeleteEvent;
import com.ikingtech.framework.sdk.context.event.TenantInitEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.core.support.LogHelper;
import com.ikingtech.framework.sdk.enums.authenticate.SignEndpointTypeEnum;
import com.ikingtech.framework.sdk.enums.domain.DomainEnum;
import com.ikingtech.framework.sdk.enums.system.tenant.TenantStatusEnum;
import com.ikingtech.framework.sdk.enums.system.user.UserCategoryEnum;
import com.ikingtech.framework.sdk.enums.system.user.UserConfigTypeEnum;
import com.ikingtech.framework.sdk.enums.system.user.UserLockTypeEnum;
import com.ikingtech.framework.sdk.enums.system.user.UserSocialTypeEnum;
import com.ikingtech.framework.sdk.enums.system.variable.VariableEnum;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.user.api.*;
import com.ikingtech.framework.sdk.user.extension.model.UserInfoEvent;
import com.ikingtech.framework.sdk.user.model.*;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.system.user.entity.*;
import com.ikingtech.platform.service.system.user.exception.UserExceptionInfo;
import com.ikingtech.platform.service.system.user.service.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.validation.annotation.Validated;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.ikingtech.framework.sdk.context.constant.SecurityConstants.DEFAULT_PASSWORD_MODIFY_TIME;

/**
 * 用户管理模块
 *
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/system/user", name = "系统管理-用户管理", description = "系统管理-用户管理")
public class UserController implements UserApi {

    private final UserRepository service;

    private final UserConfigRepository userConfigService;

    private final UserCategoryService userCategoryService;

    private final UserDeptRepository userDeptService;

    private final UserRoleRepository userRoleService;

    private final UserPostRepository userPostService;

    private final UserSocialRepository userSocialService;

    private final UserTenantRepository userTenantService;

    private final StringRedisTemplate redisTemplate;

    private final UserDeptApi userDeptApi;

    private final UserRoleApi userRoleApi;

    private final UserMenuApi userMenuApi;

    private final UserTenantApi userTenantApi;

    private final ModelConverter converter;

    private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    private final IdentityOps identityOps;

    private final ApplicationContext applicationContext;

    public static final String ADMIN_USER_ID = "00000000";

    public static final String ADMIN_USER_NAME = "admin";

    public static final String ADMIN_NAME = "平台管理员";

    @Override
    @OperationLog(value = "新增用户", dataId = "#_res.getData()")
    @Transactional(rollbackFor = Exception.class)
    public R<String> add(UserDTO user) {
        if (Boolean.TRUE.equals(this.service.usernameExist(user.getUsername()))) {
            throw new FrameworkException(UserExceptionInfo.DUPLICATE_USERNAME);
        }
        if (Boolean.TRUE.equals(this.service.phoneExist(user.getPhone()))) {
            throw new FrameworkException(UserExceptionInfo.DUPLICATE_PHONE);
        }
        if (null != user.getName() && user.getName().contains(Tools.Str.BLANK)) {
            throw new FrameworkException(UserExceptionInfo.USER_NAME_SHOULD_NOT_CONTAIN_BLANK);
        }
        if (null != user.getNickname() && user.getNickname().contains(Tools.Str.BLANK)) {
            throw new FrameworkException(UserExceptionInfo.USER_NICKNAME_SHOULD_NOT_CONTAIN_BLANK);
        }

        UserDO entity = Tools.Bean.copy(user, UserDO.class);
        entity.setId(Tools.Id.uuid());
        if (Tools.Str.isBlank(entity.getId())) {
            entity.setId(Tools.Id.uuid());
        }
        if (Tools.Str.isBlank(entity.getNickname())) {
            entity.setNickname(user.getName());
        }
        if (null == user.getPlatformUser()) {
            entity.setPlatformUser(true);
        }
        entity.setPassword(this.defaultEncodedPassword());
        entity.setPasswordModifyTime(DEFAULT_PASSWORD_MODIFY_TIME);
        this.service.saveOrUpdate(entity);
        this.insertUserDept(entity.getId(), user.getDeptIds());
        this.insertUserPost(entity.getId(), user.getPostIds());
        this.insertUserRole(entity.getId(), user.getRoleIds());
        this.insertUserTenant(entity.getId());
        this.insertUserCategory(entity.getId(), user.getCategoryCodes());
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                UserInfoEvent event = Tools.Bean.copy(user, UserInfoEvent.class);
                event.setId(entity.getId());
                event.setType(UserInfoEvent.UserInfoEventTypeEnum.ADD);
                applicationContext.publishEvent(event);
            }
        });
        return R.ok(entity.getId());
    }

    private String defaultEncodedPassword() {
        return this.passwordEncoder.encode(VariableEnum.resolveUserDefaultPassword(this.redisTemplate.opsForHash().get(CacheConstants.systemVariableFormat(Me.tenantCode()), VariableEnum.USER_DEFAULT_PASSWORD.name())));
    }

    @Override
    @OperationLog(value = "删除用户")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String id) {
        UserDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        this.userDeptService.removeByUserId(id, Me.tenantCode());
        this.userRoleService.removeByUserId(id, Me.tenantCode());
        this.userPostService.removeByUserId(id, Me.tenantCode());
        this.userTenantService.removeByUserId(id, Me.tenantCode());
        this.userConfigService.removeByUserId(id, Me.tenantCode());
        if (DomainEnum.PLATFORM_MANAGEMENT.name().equals(Me.domainCode())) {
            this.service.removeById(id);
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    UserInfoEvent event = Tools.Bean.copy(entity, UserInfoEvent.class);
                    event.setType(UserInfoEvent.UserInfoEventTypeEnum.DELETE);
                    applicationContext.publishEvent(event);
                }
            });
        }
        return R.ok();
    }

    @Override
    @OperationLog(value = "更新用户")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> update(@Validated UserDTO user) {
        this.updateUser(user);
        this.updateUserDept(user.getId(), user.getDeptIds());
        this.updateUserPost(user.getId(), user.getPostIds());
        this.updateUserRole(user.getId(), user.getRoleIds());
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                UserInfoEvent event = Tools.Bean.copy(user, UserInfoEvent.class);
                event.setType(UserInfoEvent.UserInfoEventTypeEnum.UPDATE);
                applicationContext.publishEvent(event);
            }
        });
        return R.ok();
    }

    @Override
    @OperationLog(value = "更新用户基本信息")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> updateUserInfo(UserBasicDTO user) {
        this.updateUser(user);
        return R.ok();
    }

    @Override
    public R<List<UserDTO>> page(UserQueryParamDTO queryParam) {
        List<String> dataScope = Me.dataScope();
        if (Me.invalidDataScope(dataScope)) {
            return R.ok(new ArrayList<>());
        }

        if (Tools.Coll.isNotBlank(queryParam.getDeptIds())) {
            dataScope = Tools.Coll.intersection(dataScope, queryParam.getDeptIds());
            if (Tools.Coll.isBlank(dataScope)) {
                return R.ok(new ArrayList<>());
            }
        }
        if (Tools.Str.isNotBlank(queryParam.getOrgId())) {
            dataScope = Tools.Coll.intersection(dataScope, this.userDeptApi.loadIdAllByOrgId(queryParam.getOrgId()));
            if (Tools.Coll.isBlank(dataScope)) {
                return R.ok(new ArrayList<>());
            }
        }

        List<String> userIds = Tools.Coll.isBlank(dataScope) ? new ArrayList<>() : this.userDeptService.listUserIdByDeptIds(dataScope);
        if (Tools.Coll.isBlank(userIds) && !Me.isAdmin()) {
            return R.ok(new ArrayList<>());
        }

        if (Tools.Str.isNotBlank(queryParam.getRoleId())) {
            userIds = Tools.Coll.isBlank(userIds) ?
                    this.userRoleService.listUserIdByRoleId(queryParam.getRoleId()) :
                    Tools.Coll.intersection(this.userRoleService.listUserIdByRoleId(queryParam.getRoleId()), userIds);
            if (Tools.Coll.isBlank(userIds)) {
                return R.ok(new ArrayList<>());
            }
        }
        if (Tools.Str.isNotBlank(queryParam.getPostId())) {
            userIds = Tools.Coll.isBlank(userIds) ?
                    this.userPostService.listUserIdByPostId(queryParam.getPostId()) :
                    Tools.Coll.intersection(this.userPostService.listUserIdByPostId(queryParam.getPostId()), userIds);
            if (Tools.Coll.isBlank(userIds)) {
                return R.ok(new ArrayList<>());
            }
        }
        if (Tools.Str.isNotBlank(Me.tenantCode())) {
            userIds = Tools.Coll.isBlank(userIds) ?
                    this.userTenantService.listUserIdByTenantCode(Me.tenantCode()) :
                    Tools.Coll.intersection(this.userTenantService.listUserIdByTenantCode(Me.tenantCode()), userIds);
        }
        queryParam.setAdmin(false);
        return R.ok(this.service.listPage(queryParam, userIds).convertBatch(this.converter::modelBatchConvert));
    }

    @Override
    public R<UserDTO> detail(String id) {
        UserDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        return R.ok(this.converter.modelConvert(entity));
    }

    @Override
    @OperationLog(value = "绑定用户第三方平台信息")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> bindSocial(UserSocialBindDTO userBind) {
        for (UserSocialDTO social : userBind.getSocials()) {
            if (Tools.Str.isBlank(social.getSocialNo())) {
                throw new FrameworkException(UserExceptionInfo.INVALID_SOCIAL_NO);
            }
        }
        UserDO entity = this.service.getByCredential(userBind.getUsername());
        if (null == entity) {
            if (Boolean.TRUE.equals(userBind.getAddWhenNotFound())) {
                entity = this.createUser(Tools.Id.uuid(),
                        Tools.Str.isNotBlank(userBind.getUsername()) ? userBind.getUsername() : userBind.getPhone(),
                        Tools.Str.isNotBlank(userBind.getName()) ? userBind.getName() : userBind.getUsername(),
                        this.defaultEncodedPassword(),
                        false);
                this.service.save(entity);
                this.userCategoryService.save(this.createUserCategory(entity.getId(), UserCategoryEnum.NORMAL_USER));
            } else {
                throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
            }
        } else {
            if (Boolean.TRUE.equals(userBind.getValidatePassword()) &&
                    !passwordEncoder.matches(userBind.getPassword(), entity.getPassword())) {
                throw new FrameworkException(UserExceptionInfo.INVALID_USERNAME_OR_PASSWORD);
            }
            this.identityOps.offline(entity.getId(), SignEndpointTypeEnum.WECHAT_MINI);
        }

        String userId = entity.getId();
        List<UserSocialDO> userSocialEntities = this.userSocialService.listByUserIdAndSocialTypeAndSocialId(
                userId,
                Tools.Coll.convertList(userBind.getSocials(), UserSocialDTO::getSocialType),
                Tools.Coll.convertList(userBind.getSocials(), UserSocialDTO::getSocialId)
        );
        if (Tools.Coll.isNotBlank(userSocialEntities)) {
            userSocialEntities.forEach(userSocialEntity -> this.identityOps.offline(userSocialEntity.getUserId(), UserSocialTypeEnum.valueOf(userSocialEntity.getSocialType()).signEndpointType));
            this.userSocialService.removeBatchByIds(Tools.Coll.convertList(userSocialEntities, UserSocialDO::getId));
        }
        this.userSocialService.saveBatch(Tools.Coll.convertList(userBind.getSocials(), social -> {
            UserSocialDO socialEntity = Tools.Bean.copy(social, UserSocialDO.class);
            socialEntity.setId(Tools.Id.uuid());
            socialEntity.setUserId(userId);
            return socialEntity;
        }));
        return R.ok();
    }

    @Override
    @OperationLog(value = "根据用户编号重置密码")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> resetPassword(String id) {
        UserDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        entity.setPassword(this.defaultEncodedPassword());
        entity.setPasswordModifyTime(LocalDateTime.now());
        this.service.updateById(entity);
        return R.ok();
    }

    @Override
    @OperationLog(value = "根据用户名重置密码")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> resetTenantAdminUserPassword(String tenantCode) {
        UserDO entity = this.service.getByUsername(Tools.Str.format("{}_admin", tenantCode));
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        String password = this.defaultEncodedPassword();
        entity.setPassword(password);
        entity.setPasswordModifyTime(LocalDateTime.now());
        this.service.updateById(entity);
        return R.ok();
    }

    @Override
    @OperationLog(value = "修改密码")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> modifyPassword(UserPasswordModifyParamDTO modifyParam) {
        UserDO entity = this.service.getById(modifyParam.getUserId());
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        if (!this.passwordEncoder.matches(modifyParam.getOldPassword(), entity.getPassword())) {
            throw new FrameworkException(UserExceptionInfo.INVALID_OLD_PASSWORD);
        }
        if (Tools.Str.equals(modifyParam.getOldPassword(), modifyParam.getNewPassword())) {
            throw new FrameworkException(UserExceptionInfo.PASSWORD_SAME_WITH_OLD_ONE);
        }
        entity.setPassword(this.passwordEncoder.encode(modifyParam.getNewPassword()));
        entity.setPasswordModifyTime(LocalDateTime.now());
        this.service.updateById(entity);
        return R.ok();
    }

    @Override
    @OperationLog(value = "锁定用户")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> lock(String username) {
        UserDO entity = this.service.getByUsername(username);
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        if (Boolean.TRUE.equals(entity.getLocked())) {
            return R.ok();
        }
        entity.setLocked(true);
        entity.setLockType(UserLockTypeEnum.LOCK.name());
        this.service.updateById(entity);
        this.identityOps.offline(entity.getId());
        return R.ok();
    }

    @Override
    @OperationLog(value = "解锁用户")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> unlock(String username) {
        UserDO entity = this.service.getByUsername(username);
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        if (Boolean.FALSE.equals(entity.getLocked())) {
            return R.ok();
        }
        entity.setLocked(false);
        entity.setLockType(UserLockTypeEnum.NO_LOCK.name());
        this.service.updateById(entity);
        return R.ok();
    }

    @Override
    public R<List<UserBasicDTO>> listInfoByRoleIds(BatchParam<String> roleIds) {
        List<String> userIds = this.loadUserIdByRoleIds(roleIds.getList());
        if (Tools.Coll.isBlank(userIds)) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.converter.modelInfoConvert(this.service.listByIds(userIds)));
    }

    @Override
    public R<List<UserDTO>> listByRoleIds(BatchParam<String> roleIds) {
        return R.ok(this.converter.modelBatchConvert(this.service.listByIds(this.loadUserIdByRoleIds(roleIds.getList()))));
    }

    @Override
    public R<List<String>> listIdByRoleId(String roleId) {
        return R.ok(this.userRoleService.listUserIdByRoleId(roleId));
    }

    @Override
    public R<List<UserBasicDTO>> listInfoByDeptIds(BatchParam<String> deptIds) {
        if (Tools.Coll.isBlank(deptIds.getList())) {
            return R.ok(new ArrayList<>());
        }
        List<String> userIds = this.userDeptService.listUserIdByDeptIds(deptIds.getList());
        if (Tools.Coll.isBlank(userIds)) {
            return R.ok(new ArrayList<>());
        }
        List<UserDO> entities = this.service.listByIds(userIds);
        return R.ok(this.converter.modelInfoConvert(entities));
    }

    private List<String> loadUserIdByRoleIds(List<String> roleIds) {
        if (Tools.Coll.isBlank(roleIds)) {
            return new ArrayList<>();
        }
        List<String> userIds = this.userRoleService.listUserIdByRoleIds(roleIds);
        if (Tools.Coll.isBlank(userIds)) {
            return new ArrayList<>();
        }
        return userIds;
    }

    @Override
    public R<List<UserBasicDTO>> listInfoByOrgIds(BatchParam<String> orgIds) {
        if (Tools.Coll.isBlank(orgIds.getList())) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.converter.modelInfoConvert(this.loadByOrgIds(orgIds.getList())));
    }

    @Override
    public R<List<UserDTO>> listByOrgIds(BatchParam<String> orgIds) {
        if (Tools.Coll.isBlank(orgIds.getList())) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.converter.modelBatchConvert(this.loadByOrgIds(orgIds.getList())));
    }

    private List<UserDO> loadByOrgIds(List<String> orgIds) {
        List<String> deptIds = this.userDeptApi.loadIdAllByOrgIds(orgIds);
        if (Tools.Coll.isBlank(deptIds)) {
            return new ArrayList<>();
        }
        List<String> userIds = this.userDeptService.listUserIdByDeptIds(deptIds);
        if (Tools.Coll.isBlank(userIds)) {
            return new ArrayList<>();
        }
        return this.service.listByIds(userIds);
    }

    @Override
    public R<List<String>> listIdByDeptId(String deptId) {
        return R.ok(this.userDeptService.listUserIdByDeptId(deptId));
    }

    @Override
    public R<List<String>> listIdByDeptIds(BatchParam<String> deptIds) {
        if (Tools.Coll.isBlank(deptIds.getList())) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.userDeptService.listUserIdByDeptIds(deptIds.getList()));
    }

    @Override
    public R<List<UserBasicDTO>> listInfoByPostIds(BatchParam<String> postIds) {
        if (Tools.Coll.isBlank(postIds.getList())) {
            return R.ok(new ArrayList<>());
        }
        List<String> userIds = this.userPostService.listUserIdByPostIds(postIds.getList());
        if (Tools.Coll.isBlank(userIds)) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.converter.modelInfoConvert(this.service.listByIds(userIds)));
    }

    @Override
    public R<List<String>> listIdByPostId(String postId) {
        return R.ok(this.userPostService.listUserIdByPostId(postId));
    }

    @Override
    public R<List<UserBasicDTO>> listInfoByMenuCodes(BatchParam<String> menuCodes) {
        List<String> userIds = this.userRoleApi.loadIdByMenuIds(this.userMenuApi.loadIdByCodes(menuCodes.getList(), Me.tenantCode()), Me.tenantCode());
        if (Tools.Coll.isBlank(userIds)) {
            return R.ok(new ArrayList<>());
        }
        return this.listInfoByRoleIds(BatchParam.build(userIds));
    }

    @Override
    public R<List<UserBasicDTO>> listInfoByMenuId(String menuId) {
        List<String> userIds = this.userRoleApi.loadIdByMenuId(menuId, Me.tenantCode());
        if (Tools.Coll.isBlank(userIds)) {
            return R.ok(new ArrayList<>());
        }
        return this.listInfoByRoleIds(BatchParam.build(userIds));
    }

    @Override
    public R<List<UserBasicDTO>> listInfoByIds(BatchParam<String> ids) {
        if (Tools.Coll.isBlank(ids.getList())) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.converter.modelInfoConvert(this.service.listByIds(ids.getList())));
    }

    @Override
    public R<Map<String, UserBasicDTO>> mapInfoByIds(BatchParam<String> ids) {
        if (Tools.Coll.isBlank(ids.getList())) {
            return R.ok(new HashMap<>());
        }
        return R.ok(Tools.Coll.convertMap(this.converter.modelInfoConvert(this.service.listByIds(ids.getList())), UserBasicDTO::getId));
    }

    @Override
    public R<List<UserDTO>> listByIds(BatchParam<String> ids) {
        if (Tools.Coll.isBlank(ids.getList())) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.converter.modelBatchConvert(this.service.listByIds(ids.getList())));
    }

    @Override
    public R<UserBasicDTO> getInfoById(String id) {
        UserDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        return R.ok(this.converter.modelInfoConvert(entity));
    }

    @Override
    public R<UserBasicDTO> getInfoByCredential(String credentialName) {
        UserDO entity = this.service.getByCredential(credentialName);
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        return R.ok(this.converter.modelInfoConvert(entity));
    }

    @Override
    public R<UserBasicDTO> getInfoBySocial(UserSocialQueryParamDTO queryParam) {
        UserDO entity = this.service.getById(this.userSocialService.getUserIdBySocialIdAndSocialNo(queryParam.getSocialId(), queryParam.getSocialNo()));
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        return R.ok(this.converter.modelInfoConvert(entity));
    }

    @Override
    public R<UserBasicDTO> getLoginUser() {
        UserDO entity = this.service.getById(Me.id());
        if (null == entity) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        return R.ok(this.converter.modelInfoConvert(entity));
    }

    @Override
    @OperationLog(value = "更新用户配置")
    public R<Object> updateConfig(UserConfigDTO userConfig) {
        List<UserConfigInfoDTO> configs = userConfig.getConfigInfoList();
        String userId = Boolean.TRUE.equals(userConfig.getGlobal()) ? "global" : userConfig.getUserId();
        List<UserConfigDO> entities = this.userConfigService.listByUserIdAndTenantCode(userId, Me.tenantCode());
        Map<String, UserConfigDO> configMap = Tools.Coll.convertMap(entities, UserConfigDO::getType, entity -> entity);
        List<UserConfigDO> newEntities = Tools.Coll.convertList(configs, config -> {
            UserConfigDO newEntity = configMap.computeIfAbsent(config.getType().name(), t -> {
                UserConfigDO entity = new UserConfigDO();
                entity.setId(Tools.Id.uuid());
                return entity;
            });
            newEntity.setType(config.getType().name());
            newEntity.setUserId(userId);
            newEntity.setValue(config.getValue());
            newEntity.setGlobal(userConfig.getGlobal());
            newEntity.setTenantCode(Me.tenantCode());
            return newEntity;
        });
        if (Tools.Coll.isNotBlank(newEntities)) {
            this.userConfigService.saveOrUpdateBatch(newEntities);
            this.redisTemplate.opsForHash().putAll(CacheConstants.userConfigFormat(userConfig.getUserId(), Me.tenantCode()), Tools.Coll.convertMap(newEntities, UserConfigDO::getType, UserConfigDO::getValue));
        }
        return R.ok();
    }

    @Override
    public R<UserConfigDTO> listConfigByLoginUser(Boolean global) {
        String userId = Boolean.TRUE.equals(global) ? "global" : Me.id();
        List<UserConfigDO> entities = this.userConfigService.listByUserIdAndTenantCode(userId, Me.tenantCode());
        if (Tools.Coll.isBlank(entities)) {
            return R.ok();
        }
        UserConfigDTO result = new UserConfigDTO();
        result.setUserId(userId);
        result.setGlobal(Boolean.TRUE.equals(global));
        result.setConfigInfoList(Tools.Coll.convertList(entities, entity -> Tools.Bean.copy(entity, UserConfigInfoDTO.class)));
        return R.ok(result);
    }

    @Override
    public R<String> getFrontGrayScale(Boolean global) {
        return R.ok(this.userConfigService.getValueByType(UserConfigTypeEnum.FRONT_GRAY_SCALE.name()));
    }

    @Override
    @OperationLog(value = "获取用户最近一次登录租户")
    public R<Object> getRecentTenantByLoginUser() {
        String tenantCode = this.userTenantService.getTenantCodeByRecentLogin(true);
        UserTenantDTO tenant = this.userTenantApi.loadByCode(tenantCode);
        if (null == tenant || TenantStatusEnum.NORMAL.equals(tenant.getTenantStatus())) {
            return R.ok();
        }
        return R.ok(tenant);
    }

    @Override
    @OperationLog(value = "记录用户登录租户")
    public R<Object> rememberUserLoginTenant(String tenantCode) {
        List<UserTenantDO> userTenantEntities = this.userTenantService.listByUserId(Me.id());
        if (Tools.Coll.isBlank(userTenantEntities)) {
            return R.ok();
        }
        this.userTenantService.updateBatchById(Tools.Coll.traverse(userTenantEntities, entity -> {
            entity.setRecentLogin(Tools.Str.equals(tenantCode, entity.getTenantCode()));
            return entity;
        }));
        return R.ok();
    }

    @Override
    public R<List<UserCategoryDTO>> listCategoryByLoginUser() {
        return R.ok(Tools.Array.convertList(UserCategoryEnum.values(), value -> {
            switch (value) {
                case DEVELOPER, MAINTAINER, PLATFORM_ADMINISTRATOR -> {
                    return Me.categoryCodes().contains(UserCategoryEnum.PLATFORM_ADMINISTRATOR.name());
                }
                case TENANT_ADMINISTRATOR, NORMAL_USER -> {
                    return Me.categoryCodes().contains(UserCategoryEnum.PLATFORM_ADMINISTRATOR.name()) ||
                            Me.categoryCodes().contains(UserCategoryEnum.TENANT_ADMINISTRATOR.name());
                }
                default -> {
                    return false;
                }
            }
        }, value -> {
            UserCategoryDTO category = new UserCategoryDTO();
            category.setCategory(value);
            category.setCategoryName(value.description);
            return category;
        }));
    }

    @Override
    public R<List<String>> listIdByName(String name) {
        return R.ok(this.service.listIdByName(name));
    }

    public void insertUserDept(String userId, List<String> deptIds) {
        if (Tools.Coll.isBlank(deptIds)) {
            return;
        }
        this.userDeptService.saveBatch(Tools.Coll.convertList(deptIds, Tools.Str::isNotBlank, deptId -> {
            UserDeptDO userDeptEntity = new UserDeptDO();
            userDeptEntity.setId(Tools.Id.uuid());
            userDeptEntity.setUserId(userId);
            userDeptEntity.setDeptId(deptId);
            userDeptEntity.setTenantCode(Me.tenantCode());
            return userDeptEntity;
        }));
    }

    private void insertUserPost(String userId, List<String> postIds) {
        this.userPostService.saveBatch(Tools.Coll.convertList(postIds,
                Tools.Str::isNotBlank,
                postId -> {
                    UserPostDO userPostEntity = new UserPostDO();
                    userPostEntity.setId(Tools.Id.uuid());
                    userPostEntity.setUserId(userId);
                    userPostEntity.setPostId(postId);
                    userPostEntity.setTenantCode(Me.tenantCode());
                    return userPostEntity;
                }));
    }

    private void insertUserRole(String userId, List<String> roleIds) {
        this.userRoleService.saveBatch(Tools.Coll.convertList(roleIds,
                Tools.Str::isNotBlank,
                roleId -> {
                    UserRoleDO userRoleEntity = new UserRoleDO();
                    userRoleEntity.setId(Tools.Id.uuid());
                    userRoleEntity.setUserId(userId);
                    userRoleEntity.setRoleId(roleId);
                    userRoleEntity.setTenantCode(Me.tenantCode());
                    userRoleEntity.setDomainCode(Me.domainCode());
                    userRoleEntity.setAppCode(Me.appCode());
                    return userRoleEntity;
                }));
    }

    private void insertUserTenant(String userId) {
        if (Tools.Str.isBlank(Me.tenantCode())) {
            return;
        }
        UserTenantDO entity = new UserTenantDO();
        entity.setId(Tools.Id.uuid());
        entity.setUserId(userId);
        entity.setTenantCode(Me.tenantCode());
        this.userTenantService.save(entity);
    }

    private void insertUserCategory(String userId, List<String> categoryCodes) {
        if (Tools.Coll.isBlank(categoryCodes)) {
            categoryCodes.add(UserCategoryEnum.NORMAL_USER.name());
        }
        this.userCategoryService.saveBatch(Tools.Coll.convertList(categoryCodes, categoryCode -> {
            UserCategoryDO entity = new UserCategoryDO();
            entity.setId(Tools.Id.uuid());
            entity.setUserId(userId);
            entity.setCategoryCode(categoryCode);
            return entity;
        }));
    }

    private void updateUserDept(String userId, List<String> deptIds) {
        this.userDeptService.removeByUserId(userId, Me.tenantCode());
        this.insertUserDept(userId, deptIds);
    }

    private void updateUserPost(String userId, List<String> postIds) {
        this.userPostService.removeByUserId(userId, Me.tenantCode());
        this.insertUserPost(userId, postIds);
    }

    private void updateUserRole(String userId, List<String> roleIds) {
        this.userRoleService.removeByUserId(userId, Me.tenantCode());
        this.insertUserRole(userId, roleIds);
    }

    private void updateUser(UserBasicDTO user) {
        if (!Boolean.TRUE.equals(this.service.exist(user.getId()))) {
            throw new FrameworkException(UserExceptionInfo.USER_NOT_FOUND);
        }
        if (Boolean.TRUE.equals(this.service.usernameExist(user.getId(), user.getUsername()))) {
            throw new FrameworkException(UserExceptionInfo.DUPLICATE_USERNAME);
        }
        if (Boolean.TRUE.equals(this.service.phoneExist(user.getId(), user.getPhone()))) {
            throw new FrameworkException(UserExceptionInfo.DUPLICATE_PHONE);
        }
        this.service.updateById(Tools.Bean.copy(user, UserDO.class));
    }

    @EventListener(SystemInitEvent.class)
    public void systemInitEventListener() {
        LogHelper.info("SYSTEM-INITIALIZE", "Create platform admin user.");
        List<UserCategoryDO> categoryEntities = new ArrayList<>();
        categoryEntities.add(this.createUserCategory(ADMIN_USER_ID, UserCategoryEnum.PLATFORM_ADMINISTRATOR));
        categoryEntities.add(this.createUserCategory(ADMIN_USER_ID, UserCategoryEnum.DEVELOPER));
        categoryEntities.add(this.createUserCategory(ADMIN_USER_ID, UserCategoryEnum.MAINTAINER));
        if (!this.service.exist(ADMIN_USER_ID)) {
            this.service.save(this.createUser(ADMIN_USER_ID, ADMIN_USER_NAME, ADMIN_NAME, this.defaultEncodedPassword(), true));
        }
        if (!this.userCategoryService.existByUserId(ADMIN_USER_ID)) {
            this.userCategoryService.saveBatch(categoryEntities);
        }

        if (!this.userConfigService.existByType(UserConfigTypeEnum.FRONT_GRAY_SCALE.name())) {
            UserConfigDO entity = new UserConfigDO();
            entity.setId(Tools.Id.uuid());
            entity.setGlobal(true);
            entity.setType(UserConfigTypeEnum.FRONT_GRAY_SCALE.name());
            entity.setValue("0");
            this.userConfigService.save(entity);
        }
    }

    private UserDO createUser(String userId, String username, String name, String password, Boolean masterAdmin) {
        UserDO entity = new UserDO();
        entity.setId(userId);
        entity.setUsername(username);
        entity.setName(name);
        entity.setNickname(name);
        entity.setPassword(password);
        entity.setPasswordModifyTime(DEFAULT_PASSWORD_MODIFY_TIME);
        entity.setPlatformUser(true);
        entity.setAdminUser(masterAdmin);
        return entity;
    }

    private UserCategoryDO createUserCategory(String userId, UserCategoryEnum category) {
        UserCategoryDO entity = new UserCategoryDO();
        entity.setId(Tools.Id.uuid());
        entity.setUserId(userId);
        entity.setCategoryCode(category.name());
        return entity;
    }

    @EventListener
    public void tenantInitEventListener(TenantInitEvent event) {
        String userId = Tools.Str.format("{}_{}", event.getCode(), ADMIN_USER_ID);
        this.service.saveOrUpdate(this.createUser(
                userId,
                Tools.Str.format("{}_{}", event.getCode(), ADMIN_USER_NAME),
                Tools.Str.format("{}管理员", event.getName()),
                this.defaultEncodedPassword(),
                true
        ));
        this.userCategoryService.saveOrUpdate(this.createUserCategory(userId, UserCategoryEnum.TENANT_ADMINISTRATOR));
        UserTenantDO userTenantEntity = new UserTenantDO();
        userTenantEntity.setId(Tools.Id.uuid());
        userTenantEntity.setUserId(userId);
        userTenantEntity.setTenantCode(event.getCode());
        userTenantEntity.setRecentLogin(false);
        this.userTenantService.save(userTenantEntity);
    }

    @EventListener
    public void tenantDeleteEventListener(TenantDeleteEvent event) {
        this.service.removeByUsername(Tools.Str.format("{}_{}", event.getCode(), ADMIN_USER_NAME));
        this.userDeptService.removeByTenantCode(event.getCode());
        this.userPostService.removeByTenantCode(event.getCode());
        this.userRoleService.removeByTenantCode(event.getCode());
        this.userTenantService.removeByTenantCode(event.getCode());
        this.userConfigService.removeByTenantCode(event.getCode());
    }
}
